package megadrum.mididata;

import megadrum.*;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.SysexMessage;
import javax.sound.midi.ShortMessage;

import jmidi.*;

import java.util.Vector;
import megadrum.util.Log;

public class MidiCommon {

    public static MidiDevice getMidiInputDevice(String strDeviceName) {
        try {
            if (strDeviceName != null) {
                MidiDevice.Info info = getMidiDeviceInfo(strDeviceName, false);
                if (info == null) {
//                    System.out.println("No device info found for name " + strDeviceName);
                    return null;
                }
                return MidiSystem.getMidiDevice(info);
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    public static MidiDevice getMidiOutputDevice(String strDeviceName) {
        try {
            if (strDeviceName != null) {
                MidiDevice.Info info = getMidiDeviceInfo(strDeviceName, true);
                if (info == null) {
//                    System.out.println("No device info found for name " + strDeviceName);
                    return null;
                }
                return MidiSystem.getMidiDevice(info);
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    private static MidiDevice.Info getMidiDeviceInfo(String strDeviceName, boolean bForOutput) {
        MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < aInfos.length; i++) {
            if (aInfos[i].getName().equals(strDeviceName)) {
                try {
                    MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
                    boolean bAllowsInput = (device.getMaxTransmitters() != 0);
                    boolean bAllowsOutput = (device.getMaxReceivers() != 0);
                    if ((bAllowsOutput && bForOutput) || (bAllowsInput && !bForOutput)) {
                        return aInfos[i];
                    }
                } catch (MidiUnavailableException e) {
                }
            }
        }
        return null;
    }

    //Method to forward midi messages
    public static boolean sendMidiEvent(ShortMessage message, long time, Receiver outputReceiver) { //the device is supposed to be opened yet
        try {
            outputReceiver.send(message, time);
            return true;
        } catch (Exception e) {
            MegadrumConfigTool.log.error("Trying to send MIDI message<br>The error was: " + Log.getExceptionInfo(e));
            return false;
        }
    }

    public static boolean sendSysex(byte data[], MidiDevice outputDevice) { //the device is supposed to be opened yet
        try {
            Receiver receiver = outputDevice.getReceiver();
            if (receiver == null) {
                return false;
            }

            SysexMessage sysexMessage = new SysexMessage();
            sysexMessage.setMessage(data, data.length);
            receiver.send(sysexMessage, -1 /*java.util.Calendar.getInstance().getTimeInMillis()*1000*/);
            receiver.close();
            return true;
        } catch (Exception e) {
            MegadrumConfigTool.log.error("Trying to send SYSEX message<br>The error was: " + Log.getExceptionInfo(e));
            return false;
        }
    }

    public static boolean sendMultipleSysex(byte data[][], MidiDevice outputDevice, long waitTime) { //the device is supposed to be opened yet
        try {
            Receiver receiver = outputDevice.getReceiver();
            if (receiver == null) {
                return false;
            }

            for (int i = 0; i < data.length; i++) {
                SysexMessage sysexMessage = new SysexMessage();
                sysexMessage.setMessage(data[i], data[i].length);
                receiver.send(sysexMessage, -1 /*java.util.Calendar.getInstance().getTimeInMillis()*1000*/);
                try {
                    Thread.sleep(waitTime);
                } catch (InterruptedException e) {
                }
            }
            receiver.close();
            return true;
        } catch (Exception e) {
            MegadrumConfigTool.log.error("Trying to send MULTIPLE SYSEX messages<br>The error was: " + Log.getExceptionInfo(e));
            return false;
        }
    }

    public static boolean sendMultipleSysex(byte data[][], String strDeviceName, long waitTime) { //the device is supposed to be opened yet
        try {
            MidiDevice outputDevice = null;
            Receiver receiver = null;
            if (strDeviceName != null) {
                outputDevice = getMidiOutputDevice(strDeviceName);
                if (outputDevice == null) {
//                    System.out.println("Wasn't able to retrieve MidiDevice");
                    return false;
                }
                if (!outputDevice.isOpen()) {
                    outputDevice.open();
                }
                receiver = outputDevice.getReceiver();
            } else {
                //We retrieve a Receiver for the default MidiDevice.
                receiver = MidiSystem.getReceiver();
            }
            if (receiver == null) {
//                System.out.println("Wasn't able to retrieve Receiver");
                return false;
            }
            for (int i = 0; i < data.length; i++) {
                SysexMessage sysexMessage = new SysexMessage();
                sysexMessage.setMessage(data[i], data[i].length);
                receiver.send(sysexMessage, -1 /*java.util.Calendar.getInstance().getTimeInMillis()*1000*/);
                try {
                    Thread.sleep(waitTime);
                } catch (InterruptedException e) {
                }
            }
            receiver.close();
            if (outputDevice != null) {
                outputDevice.close();
            }
            return true;
        } catch (Exception e) {
            MegadrumConfigTool.log.error("Trying to send MULTPLE SYSEX messages<br>The error was: " + Log.getExceptionInfo(e));
            return false;
        }
    }

    public static boolean sendSysexThroughMidiPort(byte data[], String outputDeviceName) {
        try {
            @SuppressWarnings("unchecked")
            Vector<String> v = MidiPort.enumerateDevices(MidiPort.MIDIPORT_OUTPUT);
            int i = 0;
            for (; i < v.size(); i++) {
                if (v.elementAt(i) != null && v.elementAt(i).equals(outputDeviceName)) {
                    break;
                }
            }
            if (i == v.size()) {
                return false;
            }

            MegadrumConfigTool.log.info("Opening MIDI port to send firmware...");
            MidiPort mp = new MidiPort(i, i);
            mp.open();
            MegadrumConfigTool.log.info("Sending firmware...");

                /*
            //split sysex
            if (split) {
                MegadrumConfigTool.log.info("sendSysexThroughMidiPort: part1 len: " + (data.length >> 1));
                try {
                    mp.writeLongMessage(data, (data.length >> 1), Common.SYSEX_SEND_TIMEOUT);
                } catch (MidiPortException e) {
                    MegadrumConfigTool.log.error("sendSysexThroughMidiPort: part1: " + Log.getExceptionInfo(e));
                }
                try {
                    Thread.sleep(5000);
                } catch (Exception e) {
                }
                byte part2[] = Converter.subByteArray(data, (data.length >> 1), data.length);
                MegadrumConfigTool.log.info("sendSysexThroughMidiPort: part2 len: " + part2.length);
                try {
                    mp.writeLongMessage(part2, part2.length, Common.SYSEX_SEND_TIMEOUT);
                } catch (MidiPortException e) {
                    MegadrumConfigTool.log.error("sendSysexThroughMidiPort: part2: " + Log.getExceptionInfo(e));
                }

            //don't split
            } else {
                */
                mp.writeLongMessage(data, data.length, Common.SYSEX_SEND_TIMEOUT);
                /*
            }
                 */

            MegadrumConfigTool.log.info("Firmware was sent. Now closing...");
            mp.close();

            return true;
        } catch (Exception e) {
            MegadrumConfigTool.log.error("Trying to send BIG SYSEX message<br>The error was: " + Log.getExceptionInfo(e));
            return false;
        }
    }

    public static boolean sendSysex(byte data[], String strDeviceName) {
        try {
            MidiDevice outputDevice = null;
            Receiver receiver = null;
            if (strDeviceName != null) {
                outputDevice = getMidiOutputDevice(strDeviceName);
                if (outputDevice == null) {
//                    System.out.println("Wasn't able to retrieve MidiDevice");
                    return false;
                }
                if (!outputDevice.isOpen()) {
                    outputDevice.open();
                }
                receiver = outputDevice.getReceiver();
            } else {
                //We retrieve a Receiver for the default MidiDevice.
                receiver = MidiSystem.getReceiver();
            }
            if (receiver == null) {
//                System.out.println("Wasn't able to retrieve Receiver");
                return false;
            }

            SysexMessage sysexMessage = new SysexMessage();
            sysexMessage.setMessage(data, data.length);

            receiver.send(sysexMessage, -1 /*java.util.Calendar.getInstance().getTimeInMillis()*1000*/);
            receiver.close();


            if (outputDevice != null) {
                outputDevice.close();
            }
            return true;
        } catch (Exception e) {
            MegadrumConfigTool.log.error("Trying to send SYSEX message<br>The error was: " + Log.getExceptionInfo(e));
            return false;
        }
    }

    public static String[] getInputDeviceList() {

        Vector<String> inputs = new Vector<String>();
        MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < aInfos.length; i++) {
            try {
                MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
                boolean isInput = (device.getMaxTransmitters() != 0);
                if (isInput) {
                    inputs.add(aInfos[i].getName());
                }
            } catch (MidiUnavailableException e) {
                // device is obviously not available...
            }
        }
        String[] list = new String[inputs.size()];
        for (int i = 0; i < list.length; i++) {
            list[i] = inputs.get(i);
        }

        return list;
    }

    public static String[] getOutputDeviceListExclude(String deviceToExclude) {

        Vector<String> outputs = new Vector<String>();
        MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < aInfos.length; i++) {
            try {
                MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
                boolean isOutput = (device.getMaxReceivers() != 0);
                if (isOutput && !aInfos[i].getName().equals(deviceToExclude)) {
                    outputs.add(aInfos[i].getName());
                }
            } catch (MidiUnavailableException e) {
                // device is obviously not available...
            }
        }
        String[] list = new String[outputs.size()];
        for (int i = 0; i < list.length; i++) {
            list[i] = outputs.get(i);
        }
        return list;
    }

    public static String[] getOutputDeviceList() {

        Vector<String> outputs = new Vector<String>();
        MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < aInfos.length; i++) {
            try {
                MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
                boolean isOutput = (device.getMaxReceivers() != 0);
                if (isOutput) {
                    outputs.add(aInfos[i].getName());
                }
            } catch (MidiUnavailableException e) {
                // device is obviously not available...
            }
        }
        String[] list = new String[outputs.size()];
        for (int i = 0; i < list.length; i++) {
            list[i] = outputs.get(i);
        }
        return list;
    }

    public static void listDevices(boolean bForInput, boolean bForOutput) {
        listDevices(bForInput, bForOutput, false);
    }

    public static void listDevices(boolean bForInput, boolean bForOutput, boolean bVerbose) {
        if (bForInput && !bForOutput) {
            System.out.println("Available MIDI IN Devices:");
        } else if (!bForInput && bForOutput) {
            System.out.println("Available MIDI OUT Devices:");
        } else {
            System.out.println("Available MIDI Devices:");
        }

        MidiDevice.Info[] aInfos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < aInfos.length; i++) {
            try {
                MidiDevice device = MidiSystem.getMidiDevice(aInfos[i]);
                boolean bAllowsInput = (device.getMaxTransmitters() != 0);
                boolean bAllowsOutput = (device.getMaxReceivers() != 0);
                if ((bAllowsInput && bForInput) ||
                        (bAllowsOutput && bForOutput)) {
                    if (bVerbose) {
                        System.out.println("" + i + "  " + (bAllowsInput ? "IN " : "   ") + (bAllowsOutput ? "OUT " : "    ") + aInfos[i].getName() + ", " + aInfos[i].getVendor() + ", " + aInfos[i].getVersion() + ", " + aInfos[i].getDescription());
                    } else {
                        System.out.println("" + i + "  " + aInfos[i].getName());
                    }
                }
            } catch (MidiUnavailableException e) {
                // device is obviously not available...
                // System.out.println(e);
            }
        }
        if (aInfos.length == 0) {
            System.out.println("[No devices available]");
        }
    }
}
